#include "SkinnedMesh.h"
#include "include/matrix4.h"
#include "include/isnwmath.h"

namespace ISNW
{
namespace scene
{
	u32 SkinnedMesh::getframenum() const
	{
		u32 num = 0;
		for(u32 i = 0; i < joints.size(); ++i)
		{
			joint* j = joints[i];
			u32 rn = 0;
			if(j->rotationkeys.size()) rn = j->rotationkeys.getlast().time;
			u32 sn = 0;
			if(j->scalekeys.size()) sn = j->scalekeys.getlast().time;
			u32 pn = 0;
			if(j->positionkeys.size()) pn = j->positionkeys.getlast().time;

			u32 n = core::_max_(rn,sn,pn);
			num = core::_max_(num,n);
		}

		return num;
	}

	ISkinnedMesh::joint* SkinnedMesh::createjoint(joint* parent)
	{
		joint* j = new joint;
		joints.push_back(j);
		if(parent)
		{
			parent->children.push_back(j);
		}

		return j;
	}

	core::array<ISkinnedMesh::joint*>& SkinnedMesh::getalljoints()
	{
		return joints;
	}

	const ISkinnedMesh::joint* SkinnedMesh::getjoint(u32 idx) const
	{
		if(idx >= joints.size()) return 0;

		return joints[idx];
	}

	ISkinnedMesh::joint* SkinnedMesh::getjoint(u32 idx)
	{
		if(idx >= joints.size()) return 0;

		return joints[idx];
	}

	const core::array<ISkinnedMesh::joint*>& SkinnedMesh::getalljoints() const
	{
		return joints;
	}

	ISkinnedMesh::weight* SkinnedMesh::createweight(joint* j)
	{
		if(!j) return 0;

		j->weights.push_back(weight());

		return &j->weights.getlast();
	}

	core::array<ISkinnedMesh::weight>& SkinnedMesh::getallweight(joint* j)
	{
		return j->weights;
	}

	ISkinnedMesh::positionkey* SkinnedMesh::createpositionkey(joint* j)
	{
		if(!j) return 0;

		j->positionkeys.push_back(positionkey());

		return &j->positionkeys.getlast();
	}

	ISkinnedMesh::scalekey* SkinnedMesh::createscalekey(joint* j)
	{
		if(!j) return 0;

		j->scalekeys.push_back(scalekey());

		return &j->scalekeys.getlast();
	}

	ISkinnedMesh::rotationkey* SkinnedMesh::createrotationkey(joint* j)
	{
		if(!j) return 0;

		j->rotationkeys.push_back(rotationkey());

		return &j->rotationkeys.getlast();
	}

	MeshBuffer* SkinnedMesh::createmeshbuffer()
	{
		MeshBuffer* mbuffer = new MeshBuffer;
		meshbuffers.push_back(mbuffer);

		return mbuffer;
	}

	core::array<MeshBuffer*>& SkinnedMesh::getallbuffer()
	{
		return meshbuffers;
	}

	const core::array<MeshBuffer*>& SkinnedMesh::getallbuffer() const
	{
		return meshbuffers;
	}

	s32 SkinnedMesh::getmeshbuffercount() const
	{
		return meshbuffers.size();
	}

	IMeshBuffer* SkinnedMesh::getmeshbuffer(s32 idx)
	{
		if(idx >= meshbuffers.size()) return 0;

		return meshbuffers[idx];
	}

	const IMeshBuffer* SkinnedMesh::getmeshbuffer(s32 idx) const
	{
		if(idx >= meshbuffers.size()) return 0;

		return meshbuffers[idx];
	}

	void SkinnedMesh::animate(u32 frame)
	{
		generateAllMatrix(frame);

		for(u32 i = 0; i < joints.size(); ++i)
		{
			animatejoint(joints[i],frame);
		}

		return;
	}

	void SkinnedMesh::animatejoint(joint* j,u32 frame)
	{
		core::matrix4f transform_inverse;
		j->transformwithparent.getinverse(transform_inverse);
		core::matrix4f TransformMatrix = j->frametransformmatrix * j->animatewithparent;

		for(u32 i = 0; i < j->weights.size(); ++i)
		{
			weight& w = j->weights[i];
			MeshBuffer* buffer = meshbuffers[w.buffer_id];
			video::vertex& v = buffer->vertex_buffer[w.vertex_id];

			core::vector3df position,normal;
			
			TransformMatrix.transformvect(w.orig_pos,position);
			TransformMatrix.transformnormal(w.orig_normal,normal);

			v.pos = position;
			v.normal = normal;
		}
	}

	void SkinnedMesh::finalize()
	{
		//! calculate the root joints
		for(u32 i = 0; i < joints.size(); ++i)
		{
			joint* j = joints[i];

			while(j && j->parent) j = j->parent;

			if(!j) continue;

			if(rootjoints.find(j) == -1)
			{
				rootjoints.push_back(j);
			}
		}

		//! back up vertex and normal 
		{
			for(u32 i = 0; i < joints.size(); ++i)
			{
				for(u32 k = 0; k < joints[i]->weights.size(); ++k)
				{
					weight& w = joints[i]->weights[k];
					w.orig_pos = meshbuffers[w.buffer_id]->vertex_buffer[w.vertex_id].pos;
					w.orig_normal = meshbuffers[w.buffer_id]->vertex_buffer[w.vertex_id].normal;
				}
			}
		}
		//! back up finished
	}

	void SkinnedMesh::calculateMatrix(joint* j,joint* parent)
	{
		if(!j) return;

		if(parent)
		{
			j->transformwithparent = j->frametransformmatrix * parent->transformwithparent;
		}
		else
		{
			j->transformwithparent = j->frametransformmatrix;
		}

		for(u32 i = 0; i < j->children.size(); ++i)
		{
			calculateMatrix(j->children[i],j);
		}
	}

	void SkinnedMesh::calculateAnimateMatrix(joint* j,joint* parent,u32 frame)
	{
		core::matrix4f translateM;
		core::matrix4f rotationM;
		core::matrix4f scaleM;

		j->lastframe = frame;

		// translate keys
		{
			s32 translatekey = -1;
			for(u32 i = 0; i < j->positionkeys.size(); ++i)
			{
				if(j->positionkeys[i].time >= frame)
				{
					translatekey = i;
					break;
				}
			}

			if(translatekey >= 0)
			{
				translateM.setranslation(j->positionkeys[translatekey].position);
			}
		}

		// rotation keys
		{
			s32 rotationkey = -1;
			for(u32 i = 0; i < j->rotationkeys.size(); ++i)
			{
				if(j->rotationkeys[i].time >= frame)
				{
					rotationkey = i;
					break;
				}
			}

			if(rotationkey >= 0)
			{
				rotationM = j->rotationkeys[rotationkey].rotation.getmatrix();
			}
		}

		// scale keys
		{
			s32 scalekey = -1;
			for(u32 i = 0; i < j->scalekeys.size(); ++i)
			{
				if(j->scalekeys[i].time >= frame)
				{
					scalekey = i;
					break;
				}
			}

			if(scalekey >= 0)
			{
				scaleM.setscale(j->scalekeys[scalekey].scale);
			}
		}

		j->animatematrix = scaleM * rotationM * translateM;

		if(parent)
		{
			j->animatewithparent = j->animatematrix * parent->animatewithparent;
		}
		else
		{
			j->animatewithparent = j->animatematrix;
		}

		for(u32 k = 0; k < j->children.size(); ++k)
		{
			calculateAnimateMatrix(j->children[k],j,frame);
		}
	}

	void SkinnedMesh::generateAllMatrix(u32 frame)
	{
		for(u32 i = 0; i < rootjoints.size(); ++i)
		{
			calculateMatrix(rootjoints[i],0);
			calculateAnimateMatrix(rootjoints[i],0,frame);
		}
	}

} // end namespace scene
} // end namespace ISNW